{***************************************************************************}
{                                                                           }
{           Spring Framework for Delphi                                     }
{                                                                           }
{           Copyright (c) 2009-2013 Spring4D Team                           }
{                                                                           }
{           http://www.spring4d.org                                         }
{                                                                           }
{***************************************************************************}
{                                                                           }
{  Licensed under the Apache License, Version 2.0 (the "License");          }
{  you may not use this file except in compliance with the License.         }
{  You may obtain a copy of the License at                                  }
{                                                                           }
{      http://www.apache.org/licenses/LICENSE-2.0                           }
{                                                                           }
{  Unless required by applicable law or agreed to in writing, software      }
{  distributed under the License is distributed on an "AS IS" BASIS,        }
{  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. }
{  See the License for the specific language governing permissions and      }
{  limitations under the License.                                           }
{                                                                           }
{***************************************************************************}

unit Spring.Collections.Enumerable;

{$I Spring.inc}
{$Q+}

interface

uses
  Generics.Defaults,
  SysUtils,
  Spring,
  Spring.Collections;

type
  Enumerable<T> = record
  private
  {$HINTS OFF}
    fDummy: Pointer;
  {$HINTS ON}
    fSource: IEnumerable<T>;

    function GetIsEmpty: Boolean;
    class function IdentityFunction(x: T): T; static;
    property Source: IEnumerable<T> read fSource write fSource;
    class function Add(const Left, Right): T; static;
  public
    class function Create: Enumerable<T>; overload; static;
    constructor Create(const collection: array of T); overload;
    constructor Create(const collection: TArray<T>); overload;

    class operator Implicit(const values: IEnumerable<T>): Enumerable<T>;
    class operator Implicit(const values: TArray<T>): Enumerable<T>;
    class operator Implicit(const values: Enumerable<T>): IEnumerable<T>;
    class operator Implicit(const values: Enumerable<T>): TArray<T>;


    function GetEnumerator: IEnumerator<T>; //inline;

    ///	<summary>
    ///	  Creates a new array which is filled with the elements in the
    ///	  collection.
    ///	</summary>
    function ToArray: TArray<T>;

    ///	<summary>
    ///	  Creates a new list which is filled with the elements in the
    ///	  collection.
    ///	</summary>
    function ToList: IList<T>;

    ///	<summary>
    ///	  Creates a new set which is filled with the elements in the
    ///	  collection.
    ///	</summary>
    function ToSet: ISet<T>;

    ///	<summary>
    ///	  Applies an accumulator function over a sequence.
    ///	</summary>
    function Aggregate(const func: TFunc<T, T, T>): T; overload;

    ///	<summary>
    ///	  Applies an accumulator function over a sequence. The specified seed
    ///	  value is used as the initial accumulator value.
    ///	</summary>
    function Aggregate<TAccumulate>(const seed: TAccumulate;
      const func: TFunc<TAccumulate, T, TAccumulate>): TAccumulate; overload;

    ///	<summary>
    ///	  Applies an accumulator function over a sequence. The specified seed
    ///	  value is used as the initial accumulator value, and the specified
    ///	  function is used to select the result value.
    ///	</summary>
    function Aggregate<TAccumulate, TResult>(const seed: TAccumulate;
      const func: TFunc<TAccumulate, T, TAccumulate>;
      const resultSelector: TFunc<TAccumulate, TResult>): TResult; overload;

    ///	<summary>
    ///	  Determines whether all elements of a sequence satisfy a condition.
    ///	</summary>
    function All(const predicate: TPredicate<T>): Boolean;

    ///	<summary>
    ///	  Determines whether a sequence contains any elements.
    ///	</summary>
    function Any: Boolean; overload;

    ///	<summary>
    ///	  Determines whether any element of a sequence satisfies a condition.
    ///	</summary>
    function Any(const predicate: TPredicate<T>): Boolean; overload;

    ///	<summary>
    ///	  Computes the average of a sequence of Double values that are obtained
    ///	  by invoking a transform function on each element of the input
    ///	  sequence.
    ///	</summary>
    function Average(const selector: TFunc<T, Double>): Double; overload;

    ///	<summary>
    ///	  Computes the average of a sequence of Int64 values that are obtained
    ///	  by invoking a transform function on each element of the input
    ///	  sequence.
    ///	</summary>
    function Average(const selector: TFunc<T, Int64>): Double; overload;

    ///	<summary>
    ///	  Concatenates two sequences.
    ///	</summary>
    function Concat(const second: array of T): Enumerable<T>; overload;

    ///	<summary>
    ///	  Concatenates two sequences.
    ///	</summary>
    function Concat(const second: IEnumerable<T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Determines whether a sequence contains a specified element by using
    ///	  the default equality comparer.
    ///	</summary>
    function Contains(const item: T): Boolean; overload;

    ///	<summary>
    ///	  Determines whether a sequence contains a specified element by using a
    ///	  specified <c>IEqualityComparer&lt;T&gt;</c>.
    ///	</summary>
    function Contains(const item: T; const comparer: IEqualityComparer<T>): Boolean; overload;

    ///	<summary>
    ///	  Returns the number of elements in a sequence.
    ///	</summary>
    function Count: Integer; overload;

    ///	<summary>
    ///	  Returns a number that represents how many elements in the specified
    ///	  sequence satisfy a condition.
    ///	</summary>
    function Count(const predicate: TPredicate<T>): Integer; overload;

    ///	<summary>
    ///	  Returns the elements of the specified sequence or the type
    ///	  parameter's default value in a singleton collection if the sequence
    ///	  is empty.
    ///	</summary>
    function DefaultIfEmpty: Enumerable<T>; overload;

    ///	<summary>
    ///	  Returns the elements of the specified sequence or the specified value
    ///	  in a singleton collection if the sequence is empty.
    ///	</summary>
    function DefaultIfEmpty(const defaultValue: T): Enumerable<T>; overload;

    ///	<summary>
    ///	  Returns distinct elements from a sequence by using the default
    ///	  equality comparer to compare values.
    ///	</summary>
    function Distinct: Enumerable<T>; overload;

    ///	<summary>
    ///	  Returns distinct elements from a sequence by using a specified
    ///	  <c>IEqualityComparer&lt;T&gt;</c> to compare values.
    ///	</summary>
    function Distinct(const comparer: IEqualityComparer<T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Returns the element at a specified index in a sequence.
    ///	</summary>
    function ElementAt(index: Integer): T;

    ///	<summary>
    ///	  Returns the element at a specified index in a sequence or a default
    ///	  value if the index is out of range.
    ///	</summary>
    function ElementAtOrDefault(index: Integer): T; overload;

    ///	<summary>
    ///	  Returns the element at a specified index in a sequence or the
    ///	  specified default value if the index is out of range.
    ///	</summary>
    function ElementAtOrDefault(index: Integer; const defaultValue: T): T; overload;

    ///	<summary>
    ///	  Produces the set difference of two sequences by using the default
    ///	  equality comparer to compare values.
    ///	</summary>
    function ExceptWith(const second: array of T): Enumerable<T>; overload;

    ///	<summary>
    ///	  Produces the set difference of two sequences by using the default
    ///	  equality comparer to compare values.
    ///	</summary>
    function ExceptWith(const second: IEnumerable<T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Produces the set difference of two sequences by using the specified
    ///	  IEqualityComparer{T} to compare values.
    ///	</summary>
    function ExceptWith(const second: IEnumerable<T>; const comparer: IEqualityComparer<T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Performs the specified action on each element of a sequence.
    ///	</summary>
    procedure ForEach(const action: TAction<T>);

    ///	<summary>
    ///	  Returns the first element of a sequence.
    ///	</summary>
    function First: T; overload;

    ///	<summary>
    ///	  Returns the first element in a sequence that satisfies a specified
    ///	  condition.
    ///	</summary>
    function First(const predicate: TPredicate<T>): T; overload;

    ///	<summary>
    ///	  Returns the first element of a sequence, or a default value if the
    ///	  sequence contains no elements.
    ///	</summary>
    function FirstOrDefault: T; overload;

    ///	<summary>
    ///	  Returns the first element of a sequence, or the specified default
    ///	  value if the sequence contains no elements.
    ///	</summary>
    function FirstOrDefault(const defaultValue: T): T; overload;

    ///	<summary>
    ///	  Returns the first element of the sequence that satisfies a condition
    ///	  or a default value if no such element is found.
    ///	</summary>
    function FirstOrDefault(const predicate: TPredicate<T>): T; overload;

    ///	<summary>
    ///	  Returns the first element of the sequence that satisfies a condition
    ///	  or the specified default value if no such element is found.
    ///	</summary>
    function FirstOrDefault(const predicate: TPredicate<T>; const defaultValue: T): T; overload;

{$IFNDEF DELPHIXE4_UP}

    ///	<summary>
    ///	  Groups the elements of a sequence according to a specified key
    ///	  selector function.
    ///	</summary>
    function GroupBy<TKey>(const keySelector: TFunc<T, TKey>): Enumerable<IGrouping<TKey, T>>; overload;

    ///	<summary>
    ///	  Groups the elements of a sequence according to a specified key
    ///	  selector function and compares the keys by using a specified
    ///	  comparer.
    ///	</summary>
    function GroupBy<TKey>(const keySelector: TFunc<T, TKey>;
      const comparer: IEqualityComparer<TKey>): Enumerable<IGrouping<TKey, T>>; overload;

    ///	<summary>
    ///	  Groups the elements of a sequence according to a specified key
    ///	  selector function and projects the elements for each group by using a
    ///	  specified function.
    ///	</summary>
    function GroupBy<TKey, TElement>(const keySelector: TFunc<T, TKey>;
      const elementSelector: TFunc<T, TElement>): Enumerable<IGrouping<TKey, TElement>>; overload;

    ///	<summary>
    ///	  Groups the elements of a sequence according to a key selector
    ///	  function. The keys are compared by using a comparer and each group's
    ///	  elements are projected by using a specified function.
    ///	</summary>
    function GroupBy<TKey, TElement>(const keySelector: TFunc<T, TKey>;
      const elementSelector: TFunc<T, TElement>;
      const comparer: IEqualityComparer<TKey>): Enumerable<IGrouping<TKey, TElement>>; overload;
{$ENDIF}

    ///	<summary>
    ///	  Groups the elements of a sequence according to a specified key
    ///	  selector function and creates a result value from each group and its
    ///	  key.
    ///	</summary>
    function GroupBy<TKey, TResult>(const keySelector: TFunc<T, TKey>;
      const resultSelector: TFunc<TKey, IEnumerable<T>, TResult>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Groups the elements of a sequence according to a specified key
    ///	  selector function and creates a result value from each group and its
    ///	  key. The keys are compared by using a specified comparer.
    ///	</summary>
    function GroupBy<TKey, TResult>(const keySelector: TFunc<T, TKey>;
      const resultSelector: TFunc<TKey, IEnumerable<T>, TResult>;
      const comparer: IEqualityComparer<TKey>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Groups the elements of a sequence according to a specified key
    ///	  selector function and creates a result value from each group and its
    ///	  key. The elements of each group are projected by using a specified
    ///	  function.
    ///	</summary>
    function GroupBy<TKey, TElement, TResult>(const keySelector: TFunc<T, TKey>;
      const elementSelector: TFunc<T, TElement>;
      const resultSelector: TFunc<TKey, IEnumerable<TElement>, TResult>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Groups the elements of a sequence according to a specified key
    ///	  selector function and creates a result value from each group and its
    ///	  key. Key values are compared by using a specified comparer, and the
    ///	  elements of each group are projected by using a specified function.
    ///	</summary>
    function GroupBy<TKey, TElement, TResult>(const keySelector: TFunc<T, TKey>;
      const elementSelector: TFunc<T, TElement>;
      const resultSelector: TFunc<TKey, IEnumerable<TElement>, TResult>;
      const comparer: IEqualityComparer<TKey>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Correlates the elements of two sequences based on equality of keys
    ///	  and groups the results. The default equality comparer is used to
    ///	  compare keys.
    ///	</summary>
    function GroupJoin<TInner, TKey, TResult>(const inner: IEnumerable<TInner>;
      const outerKeySelector: TFunc<T, TKey>;
      const innerKeySelector: TFunc<TInner, TKey>;
      const resultSelector: TFunc<T, IEnumerable<TInner>, TResult>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Correlates the elements of two sequences based on key equality and
    ///	  groups the results. A specified IEqualityComparer{T} is used to
    ///	  compare keys.
    ///	</summary>
    function GroupJoin<TInner, TKey, TResult>(const inner: IEnumerable<TInner>;
      const outerKeySelector: TFunc<T, TKey>;
      const innerKeySelector: TFunc<TInner, TKey>;
      const resultSelector: TFunc<T, IEnumerable<TInner>, TResult>;
      const comparer: IEqualityComparer<TKey>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Produces the set intersection of two sequences by using the default
    ///	  equality comparer to compare values.
    ///	</summary>
    function IntersectWith(const second: array of T): Enumerable<T>; overload;

    ///	<summary>
    ///	  Produces the set intersection of two sequences by using the default
    ///	  equality comparer to compare values.
    ///	</summary>
    function IntersectWith(const second: IEnumerable<T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Produces the set intersection of two sequences by using the specified
    ///	  IEqualityComparer{T} to compare values.
    ///	</summary>
    function IntersectWith(const second: IEnumerable<T>; const comparer: IEqualityComparer<T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Correlates the elements of two sequences based on matching keys. The
    ///	  default equality comparer is used to compare keys.
    ///	</summary>
    function Join<TInner, TKey, TResult>(const inner: IEnumerable<TInner>;
      const outerKeySelector: TFunc<T, TKey>;
      const innerKeySelector: TFunc<TInner, TKey>;
      const resultSelector: TFunc<T, TInner, TResult>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Correlates the elements of two sequences based on matching keys. A
    ///	  specified IEqualityComparer{T} is used to compare keys.
    ///	</summary>
    function Join<TInner, TKey, TResult>(const inner: IEnumerable<TInner>;
      const outerKeySelector: TFunc<T, TKey>;
      const innerKeySelector: TFunc<TInner, TKey>;
      const resultSelector: TFunc<T, TInner, TResult>;
      const comparer: IEqualityComparer<TKey>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Returns the last element of a sequence.
    ///	</summary>
    function Last: T; overload;

    ///	<summary>
    ///	  Returns the last element of a sequence that satisfies a specified
    ///	  condition.
    ///	</summary>
    function Last(const predicate: TPredicate<T>): T; overload;

    ///	<summary>
    ///	  Returns the last element of a sequence, or a default value if the
    ///	  sequence contains no elements.
    ///	</summary>
    function LastOrDefault: T; overload;

    ///	<summary>
    ///	  Returns the last element of a sequence, or the specified default
    ///	  value if the sequence contains no elements.
    ///	</summary>
    function LastOrDefault(const defaultValue: T): T; overload;

    ///	<summary>
    ///	  Returns the last element of a sequence that satisfies a condition or
    ///	  a default value if no such element is found.
    ///	</summary>
    function LastOrDefault(const predicate: TPredicate<T>): T; overload;

    ///	<summary>
    ///	  Returns the last element of a sequence that satisfies a condition or
    ///	  the specified default value if no such element is found.
    ///	</summary>
    function LastOrDefault(const predicate: TPredicate<T>; const defaultValue: T): T; overload;

    ///	<summary>
    ///	  Returns the maximum value in a sequence.
    ///	</summary>
    function Max: T; overload;

    ///	<summary>
    ///	  Invokes a transform function on each element of a sequence and
    ///	  returns the maximum Double value.
    ///	</summary>
    function Max(const selector: TFunc<T, Double>): Double; overload;

    ///	<summary>
    ///	  Invokes a transform function on each element of a sequence and
    ///	  returns the maximum Int64 value.
    ///	</summary>
    function Max(const selector: TFunc<T, Int64>): Int64; overload;

    ///	<summary>
    ///	  Returns the elements with the maximum key value by using the default
    ///	  comparer to compare key values.
    ///	</summary>
    function MaxBy<TKey>(const keySelector: TFunc<T, TKey>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Returns the elements with the minimum key value by using the
    ///	  specified comparer to compare key values.
    ///	</summary>
    function MaxBy<TKey>(const keySelector: TFunc<T, TKey>; const comparer: IComparer<TKey>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Returns the minimum value in a sequence.
    ///	</summary>
    function Min: T; overload;

    ///	<summary>
    ///	  Invokes a transform function on each element of a sequence and
    ///	  returns the minumum Double value.
    ///	</summary>
    function Min(const selector: TFunc<T, Double>): Double; overload;

    ///	<summary>
    ///	  Invokes a transform function on each element of a sequence and
    ///	  returns the minimum Int64 value.
    ///	</summary>
    function Min(const selector: TFunc<T, Int64>): Int64; overload;

    ///	<summary>
    ///	  Returns the elements with the minimum key value by using the default
    ///	  comparer to compare key values.
    ///	</summary>
    function MinBy<TKey>(const keySelector: TFunc<T, TKey>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Returns the elements with the minimum key value by using the
    ///	  specified comparer to compare key values.
    ///	</summary>
    function MinBy<TKey>(const keySelector: TFunc<T, TKey>; const comparer: IComparer<TKey>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Sorts the elements of a sequence in ascending order according to a
    ///	  key.
    ///	</summary>
    function OrderBy<TKey>(const keySelector: TFunc<T, TKey>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Sorts the elements of a sequence in ascending order by using a
    ///	  specified comparer.
    ///	</summary>
    function OrderBy<TKey>(const keySelector: TFunc<T, TKey>;
      const comparer: IComparer<TKey>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Sorts the elements of a sequence in descending order according to a
    ///	  key.
    ///	</summary>
    function OrderByDescending<TKey>(
      const keySelector: TFunc<T, TKey>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Sorts the elements of a sequence in descending order by using a
    ///	  specified comparer.
    ///	</summary>
    function OrderByDescending<TKey>(const keySelector: TFunc<T, TKey>;
      const comparer: IComparer<TKey>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Inverts the order of the elements in a sequence.
    ///	</summary>
    function Reverse: Enumerable<T>;

    ///	<summary>
    ///	  Projects each element of a sequence into a new form.
    ///	</summary>
    function Select(const selector: TFunc<T, T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Projects each element of a sequence into a new form.
    ///	</summary>
    function Select<TResult>(
      const selector: TFunc<T, TResult>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Projects each element of a sequence into a new form by incorporating
    ///	  the element's index.
    ///	</summary>
    function Select(const selector: TFunc<T, Integer, T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Projects each element of a sequence into a new form by incorporating
    ///	  the element's index.
    ///	</summary>
    function Select<TResult>(
      const selector: TFunc<T, Integer, TResult>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Projects each element of a sequence to an IEnumerable{T} and flattens
    ///	  the resulting sequences into one sequence.
    ///	</summary>
    function SelectMany<TResult>(
      const selector: TFunc<T, IEnumerable<TResult>>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Projects each element of a sequence to an IEnumerable{T}, and
    ///	  flattens the resulting sequences into one sequence. The index of each
    ///	  source element is used in the projected form of that element.
    ///	</summary>
    function SelectMany<TResult>(
      const selector: TFunc<T, Integer, IEnumerable<TResult>>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Projects each element of a sequence to an IEnumerable{T}, flattens
    ///	  the resulting sequences into one sequence, and invokes a result
    ///	  selector function on each element therein.
    ///	</summary>
    function SelectMany<TCollection, TResult>(
      const collectionSelector: TFunc<T, IEnumerable<TCollection>>;
      const resultSelector: TFunc<T, TCollection, TResult>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Projects each element of a sequence to an IEnumerable{T}, flattens
    ///	  the resulting sequences into one sequence, and invokes a result
    ///	  selector function on each element therein. The index of each source
    ///	  element is used in the intermediate projected form of that element.
    ///	</summary>
    function SelectMany<TCollection, TResult>(
      const collectionSelector: TFunc<T, Integer, IEnumerable<TCollection>>;
      const resultSelector: TFunc<T, TCollection, TResult>): Enumerable<TResult>; overload;

    ///	<summary>
    ///	  Determines whether two sequences are equal by comparing the elements
    ///	  by using the default equality comparer for their type.
    ///	</summary>
    function SequenceEqual(const second: array of T): Boolean; overload;

    ///	<summary>
    ///	  Determines whether two sequences are equal by comparing the elements
    ///	  by using the default equality comparer for their type.
    ///	</summary>
    function SequenceEqual(const second: IEnumerable<T>): Boolean; overload;

    ///	<summary>
    ///	  Determines whether two sequences are equal by comparing their
    ///	  elements by using a specified IEqualityComparer{T}.
    ///	</summary>
    function SequenceEqual(const second: IEnumerable<T>; comparer: IEqualityComparer<T>): Boolean; overload;

    ///	<summary>
    ///	  Returns the only element of a sequence, and throws an exception if
    ///	  there is not exactly one element in the sequence.
    ///	</summary>
    function Single: T; overload;

    ///	<summary>
    ///	  Returns the only element of a sequence, and throws an exception if
    ///	  there is not exactly one element in the sequence.
    ///	</summary>
    function Single(const predicate: TPredicate<T>): T; overload;

    ///	<summary>
    ///	  Returns the only element of a sequence, or a default value if the
    ///	  sequence is empty; this method throws an exception if there is more
    ///	  than one element in the sequence.
    ///	</summary>
    function SingleOrDefault: T; overload;

    ///	<summary>
    ///	  Returns the only element of a sequence, or the specified default
    ///	  value if the sequence is empty; this method throws an exception if
    ///	  there is more than one element in the sequence.
    ///	</summary>
    function SingleOrDefault(const defaultValue: T): T; overload;

    ///	<summary>
    ///	  Returns the only element of a sequence that satisfies a specified
    ///	  condition or a default value if no such element exists; this method
    ///	  throws an exception if more than one element satisfies the condition.
    ///	</summary>
    function SingleOrDefault(const predicate: TPredicate<T>): T; overload;

    ///	<summary>
    ///	  Returns the only element of a sequence that satisfies a specified
    ///	  condition or the specified default value if no such element exists;
    ///	  this method throws an exception if more than one element satisfies
    ///	  the condition.
    ///	</summary>
    function SingleOrDefault(const predicate: TPredicate<T>; const defaultValue: T): T; overload;

    ///	<summary>
    ///	  Bypasses a specified number of elements in a sequence and then
    ///	  returns the remaining elements.
    ///	</summary>
    function Skip(count: Integer): Enumerable<T>;

    ///	<summary>
    ///	  Bypasses elements in a sequence as long as a specified condition is
    ///	  true and then returns the remaining elements.
    ///	</summary>
    function SkipWhile(const predicate: TPredicate<T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Bypasses elements in a sequence as long as a specified condition is
    ///	  true and then returns the remaining elements. The element's index is
    ///	  used in the logic of the predicate function.
    ///	</summary>
    function SkipWhile(const predicate: TFunc<T, Integer, Boolean>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Computes the sum of a sequence.
    ///	</summary>
    function Sum: T; overload;

    ///	<summary>
    ///	  Computes the sum of the sequence of Integer values that are obtained
    ///	  by invoking a transform function on each element of the input
    ///	  sequence.
    ///	</summary>
    function Sum(const selector: TFunc<T, Integer>): Integer; overload;

    ///	<summary>
    ///	  Computes the sum of the sequence of nullable Integer values that are
    ///	  obtained by invoking a transform function on each element of the
    ///	  input sequence.
    ///	</summary>
    function Sum(const selector: TFunc<T, Nullable<Integer>>): Nullable<Integer>; overload;

    ///	<summary>
    ///	  Computes the sum of the sequence of Int64 values that are obtained by
    ///	  invoking a transform function on each element of the input sequence.
    ///	</summary>
    function Sum(const selector: TFunc<T, Int64>): Int64; overload;

    ///	<summary>
    ///	  Computes the sum of the sequence of nullable Int64 values that are
    ///	  obtained by invoking a transform function on each element of the
    ///	  input sequence.
    ///	</summary>
    function Sum(const selector: TFunc<T, Nullable<Int64>>): Nullable<Int64>; overload;

    ///	<summary>
    ///	  Computes the sum of the sequence of Single values that are obtained
    ///	  by invoking a transform function on each element of the input
    ///	  sequence.
    ///	</summary>
    function Sum(const selector: TFunc<T, Single>): Single; overload;

    ///	<summary>
    ///	  Computes the sum of the sequence of nullable Single values that are
    ///	  obtained by invoking a transform function on each element of the
    ///	  input sequence.
    ///	</summary>
    function Sum(const selector: TFunc<T, Nullable<Single>>): Nullable<Single>; overload;

    ///	<summary>
    ///	  Computes the sum of the sequence of Double values that are obtained
    ///	  by invoking a transform function on each element of the input
    ///	  sequence.
    ///	</summary>
    function Sum(const selector: TFunc<T, Double>): Double; overload;

    ///	<summary>
    ///	  Computes the sum of the sequence of nullable Double values that are
    ///	  obtained by invoking a transform function on each element of the
    ///	  input sequence.
    ///	</summary>
    function Sum(const selector: TFunc<T, Nullable<Double>>): Nullable<Double>; overload;

    ///	<summary>
    ///	  Returns a specified number of contiguous elements from the start of a
    ///	  sequence.
    ///	</summary>
    function Take(count: Integer): Enumerable<T>;

    ///	<summary>
    ///	  Returns elements from a sequence as long as a specified condition is
    ///	  true.
    ///	</summary>
    function TakeWhile(const predicate: TPredicate<T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Returns elements from a sequence as long as a specified condition is
    ///	  true. The element's index is used in the logic of the predicate
    ///	  function.
    ///	</summary>
    function TakeWhile(const predicate: TFunc<T, Integer, Boolean>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Performs a subsequent ordering of the elements in a sequence in
    ///	  ascending order according to a key.
    ///	</summary>
    function ThenBy<TKey>(const keySelector: TFunc<T, TKey>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Performs a subsequent ordering of the elements in a sequence in
    ///	  ascending order by using a specified comparer.
    ///	</summary>
    function ThenBy<TKey>(const keySelector: TFunc<T, TKey>;
      const comparer: IComparer<TKey>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Performs a subsequent ordering of the elements in a sequence in
    ///	  descending order, according to a key.
    ///	</summary>
    function ThenByDescending<TKey>(
      const keySelector: TFunc<T, TKey>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Performs a subsequent ordering of the elements in a sequence in
    ///	  descending order by using a specified comparer.
    ///	</summary>
    function ThenByDescending<TKey>(const keySelector: TFunc<T, TKey>;
      const comparer: IComparer<TKey>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Creates a Lookup{TKey, TElement} from an IEnumerable{T} according to
    ///	  a specified key selector function.
    ///	</summary>
    function ToLookup(const keySelector: TFunc<T, T>): ILookup<T, T>; overload;

    ///	<summary>
    ///	  Creates a Lookup{TKey, TElement} from an IEnumerable{T} according to
    ///	  a specified key selector function.
    ///	</summary>
    function ToLookup<TKey>(const keySelector: TFunc<T, TKey>): ILookup<TKey, T>; overload;

    ///	<summary>
    ///	  Creates a Lookup{TKey, TElement} from an IEnumerable{T} according to
    ///	  a specified key selector function and key comparer.
    ///	</summary>
    function ToLookup(const keySelector: TFunc<T, T>;
      const comparer: IEqualityComparer<T>): ILookup<T, T>; overload;

    ///	<summary>
    ///	  Creates a Lookup{TKey, TElement} from an IEnumerable{T} according to
    ///	  a specified key selector function and key comparer.
    ///	</summary>
    function ToLookup<TKey>(const keySelector: TFunc<T, TKey>;
      const comparer: IEqualityComparer<TKey>): ILookup<TKey, T>; overload;

    ///	<summary>
    ///	  Creates a TLookup{TKey, TElement} from an IEnumerable{T} according to
    ///	  specified key selector and element selector functions.
    ///	</summary>
    function ToLookup<TKey, TElement>(const keySelector: TFunc<T, TKey>;
      const elementSelector: TFunc<T, TElement>): ILookup<TKey, TElement>; overload;

    ///	<summary>
    ///	  Creates a Lookup{TKey, TElement} from an IEnumerable{T} according to
    ///	  a specified key selector function, a comparer and an element selector
    ///	  function.
    ///	</summary>
    function ToLookup<TKey, TElement>(const keySelector: TFunc<T, TKey>;
      const elementSelector: TFunc<T, TElement>;
      const comparer: IEqualityComparer<TKey>): ILookup<TKey, TElement>; overload;

    ///	<summary>
    ///	  Try getting the first element of a sequence.
    ///	</summary>
    function TryGetFirst(out value: T): Boolean; overload;

    ///	<summary>
    ///	  Returns the first element in a sequence that satisfies a specified
    ///	  condition.
    ///	</summary>
    function TryGetFirst(out value: T; const predicate: TPredicate<T>): Boolean; overload;

    ///	<summary>
    ///	  Try getting the last element of a sequence.
    ///	</summary>
    function TryGetLast(out value: T): Boolean; overload;

    ///	<summary>
    ///	  Try getting the last element in a sequence that satisfies a specified
    ///	  condition.
    ///	</summary>
    function TryGetLast(out value: T; const predicate: TPredicate<T>): Boolean; overload;

    ///	<summary>
    ///	  Produces the set union of two sequences by using the default equality
    ///	  comparer.
    ///	</summary>
    function UnionWith(const second: array of T): Enumerable<T>; overload;

    ///	<summary>
    ///	  Produces the set union of two sequences by using the default equality
    ///	  comparer.
    ///	</summary>
    function UnionWith(const second: IEnumerable<T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Produces the set union of two sequences by using a specified
    ///	  IEqualityComparer{T}.
    ///	</summary>
    function UnionWith(const second: IEnumerable<T>; const comparer: IEqualityComparer<T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Filters a sequence of values based on a predicate.
    ///	</summary>
    function Where(const predicate: TPredicate<T>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Filters a sequence of values based on a predicate. Each element's
    ///	  index is used in the logic of the predicate function.
    ///	</summary>
    function Where(const predicate: TFunc<T, Integer, Boolean>): Enumerable<T>; overload;

    ///	<summary>
    ///	  Applies a specified function to the corresponding elements of two
    ///	  sequences, which produces a sequence of the results.
    ///	</summary>
    function Zip<TSecond, TResult>(const second: IEnumerable<TSecond>;
      const resultSelector: TFunc<T, TSecond, TResult>): Enumerable<TResult>;

    property IsEmpty: Boolean read GetIsEmpty;
  end;

  Enumerable = record
  public
    class function Empty<T>: Enumerable<T>; static;
    class function Range(start, count: Integer): Enumerable<Integer>; static;
  end;

implementation

uses
  TypInfo,
  Spring.Collections.Extensions,
  Spring.ResourceStrings;


{$REGION 'Enumerable<T>'}

class function Enumerable<T>.Create: Enumerable<T>;
begin
  Result := TArrayIterator<T>.Create();
end;

constructor Enumerable<T>.Create(const collection: array of T);
begin
  fSource := TArrayIterator<T>.Create(collection);
end;

constructor Enumerable<T>.Create(const collection: TArray<T>);
begin
  fSource := TArrayIterator<T>.Create(collection);
end;

class operator Enumerable<T>.Implicit(
  const values: IEnumerable<T>): Enumerable<T>;
begin
  Result.Source := values;
end;

class operator Enumerable<T>.Implicit(
 const values: Enumerable<T>): IEnumerable<T>;
begin
  Result := values.Source;
end;

class operator Enumerable<T>.Implicit(const values: Enumerable<T>): TArray<T>;
begin
  Result := values.Source.ToArray;
end;

class operator Enumerable<T>.Implicit(const values: TArray<T>): Enumerable<T>;
begin
  Result.Source := TArrayIterator<T>.Create(values);
end;

class function Enumerable<T>.IdentityFunction(x: T): T;
begin
  Result := x;
end;

class function Enumerable<T>.Add(const Left, Right): T;
begin
  case PTypeInfo(TypeInfo(T)).Kind of
    tkInteger: PInteger(@Result)^ := Integer(Left) + Integer(Right);
    tkInt64: PInt64(@Result)^ := Int64(Left) + Int64(Right);
    tkFloat:
    begin
      case GetTypeData(TypeInfo(T)).FloatType of
        ftSingle: PSingle(@Result)^ := System.Single(Left) + System.Single(Right);
        ftDouble: PDouble(@Result)^ := Double(Left) + Double(Right);
        ftExtended: PExtended(@Result)^ := Extended(Left) + Extended(Right);
        ftComp: PComp(@Result)^ := Comp(Left) + Comp(Right);
        ftCurr: PCurrency(@Result)^ := Currency(Left) + Currency(Right);
      end;
    end;
  end;
end;

function Enumerable<T>.Aggregate(const func: TFunc<T, T, T>): T;
var
  enumerator: IEnumerator<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(func), 'func');

  enumerator := GetEnumerator;
  if not enumerator.MoveNext then
    raise EInvalidOperationException.CreateRes(@SNoElements);
  Result := enumerator.Current;
  while enumerator.MoveNext do
    Result := func(Result, enumerator.Current);
end;

function Enumerable<T>.Aggregate<TAccumulate>(const seed: TAccumulate;
  const func: TFunc<TAccumulate, T, TAccumulate>): TAccumulate;
var
  enumerator: IEnumerator<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(func), 'func');

  enumerator := GetEnumerator;
  Result := seed;
  while enumerator.MoveNext do
    Result := func(Result, enumerator.Current);
end;

function Enumerable<T>.Aggregate<TAccumulate, TResult>(const seed: TAccumulate;
  const func: TFunc<TAccumulate, T, TAccumulate>;
  const resultSelector: TFunc<TAccumulate, TResult>): TResult;
var
  enumerator: IEnumerator<T>;
  accumulate: TAccumulate;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(func), 'func');
  TArgument.CheckNotNull(Assigned(resultSelector), 'resultSelector');

  enumerator := GetEnumerator;
  accumulate := seed;
  while enumerator.MoveNext do
    accumulate := func(accumulate, enumerator.Current);
  Result := resultSelector(accumulate);
end;

function Enumerable<T>.All(const predicate: TPredicate<T>): Boolean;
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := True;
  for item in Source do
    if not predicate(item) then
      Exit(False);
end;

function Enumerable<T>.Any: Boolean;
var
  enumerator: IEnumerator;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  enumerator := GetEnumerator;
  Result := enumerator.MoveNext;
end;

function Enumerable<T>.Any(const predicate: TPredicate<T>): Boolean;
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := False;
  for item in Source do
    if predicate(item) then
      Exit(True);
end;

function Enumerable<T>.Average(const selector: TFunc<T, Double>): Double;
var
  sum: Double;
  count: Int64;
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  sum := 0;
  count := 0;
  for item in Source do
  begin
    sum := sum + selector(item);
    Inc(count);
  end;
  if count > 0 then
    Result := sum / count
  else
    raise EInvalidOperationException.CreateRes(@SNoElements);
end;

function Enumerable<T>.Average(const selector: TFunc<T, Int64>): Double;
var
  sum: Int64;
  count: Int64;
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  sum := 0;
  count := 0;
  for item in Self do
  begin
    sum := sum + selector(item);
    Inc(count);
  end;
  if count > 0 then
    Result := sum / count
  else
    raise EInvalidOperationException.CreateRes(@SNoElements);
end;

function Enumerable<T>.Concat(const second: array of T): Enumerable<T>;
begin
  Result := Concat(TArrayIterator<T>.Create(second));
end;

function Enumerable<T>.Concat(const second: IEnumerable<T>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'first');
  TArgument.CheckNotNull(Assigned(second), 'second');

  Result := TConcatIterator<T>.Create(Source, second);
end;

function Enumerable<T>.Contains(const item: T): Boolean;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.Contains(item);
end;

function Enumerable<T>.Contains(const item: T;
  const comparer: IEqualityComparer<T>): Boolean;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.Contains(item, comparer);
end;

function Enumerable<T>.Count: Integer;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.Count;
end;

function Enumerable<T>.Count(const predicate: TPredicate<T>): Integer;
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := 0;
  for item in Self do
    if predicate(item) then
      Inc(Result);
end;

function Enumerable<T>.DefaultIfEmpty: Enumerable<T>;
begin
  Result := DefaultIfEmpty(Default(T));
end;

function Enumerable<T>.DefaultIfEmpty(const defaultValue: T): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := TDefaultIfEmptyIterator<T>.Create(Source, defaultValue);
end;

function Enumerable<T>.Distinct: Enumerable<T>;
begin
  Result := Distinct(nil);
end;

function Enumerable<T>.Distinct(
  const comparer: IEqualityComparer<T>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := TDistinctIterator<T>.Create(Source, comparer);
end;

function Enumerable<T>.ElementAt(index: Integer): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.ElementAt(index);
end;

function Enumerable<T>.ElementAtOrDefault(index: Integer): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.ElementAtOrDefault(index);
end;

function Enumerable<T>.ElementAtOrDefault(index: Integer;
  const defaultValue: T): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.ElementAtOrDefault(index, defaultValue);
end;

function Enumerable<T>.ExceptWith(const second: array of T): Enumerable<T>;
begin
  Result := ExceptWith(TArrayIterator<T>.Create(second));
end;

function Enumerable<T>.ExceptWith(const second: IEnumerable<T>): Enumerable<T>;
begin
  Result := ExceptWith(second, nil);
end;

function Enumerable<T>.ExceptWith(const second: IEnumerable<T>;
  const comparer: IEqualityComparer<T>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'first');
  TArgument.CheckNotNull(Assigned(second), 'second');

  Result := TExceptIterator<T>.Create(Source, second, comparer);
end;

function Enumerable<T>.First: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.First;
end;

function Enumerable<T>.First(const predicate: TPredicate<T>): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := Source.First(predicate);
end;

function Enumerable<T>.FirstOrDefault: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.FirstOrDefault;
end;

function Enumerable<T>.FirstOrDefault(const defaultValue: T): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.FirstOrDefault(defaultValue);
end;

function Enumerable<T>.FirstOrDefault(const predicate: TPredicate<T>): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := Source.FirstOrDefault(predicate);
end;

function Enumerable<T>.FirstOrDefault(const predicate: TPredicate<T>;
  const defaultValue: T): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := Source.FirstOrDefault(predicate, defaultValue);
end;

procedure Enumerable<T>.ForEach(const action: TAction<T>);
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(action), 'action');

  for item in Source do
    action(item);
end;

function Enumerable<T>.GetEnumerator: IEnumerator<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.GetEnumerator;
end;

function Enumerable<T>.GetIsEmpty: Boolean;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.IsEmpty;
end;

{$IFNDEF DELPHIXE4_UP}
function Enumerable<T>.GroupBy<TKey>(
  const keySelector: TFunc<T, TKey>): Enumerable<IGrouping<TKey, T>>;
begin
  Result := GroupBy<TKey, T>(keySelector, IdentityFunction, nil);
end;

function Enumerable<T>.GroupBy<TKey>(const keySelector: TFunc<T, TKey>;
  const comparer: IEqualityComparer<TKey>): Enumerable<IGrouping<TKey, T>>;
begin
  Result := GroupBy<TKey, T>(keySelector, IdentityFunction, comparer)
end;

function Enumerable<T>.GroupBy<TKey, TElement>(
  const keySelector: TFunc<T, TKey>;
  const elementSelector: TFunc<T, TElement>): Enumerable<IGrouping<TKey, TElement>>;
begin
  Result := GroupBy<TKey, TElement>(keySelector, elementSelector, nil);
end;

function Enumerable<T>.GroupBy<TKey, TElement>(
  const keySelector: TFunc<T, TKey>; const elementSelector: TFunc<T, TElement>;
  const comparer: IEqualityComparer<TKey>): Enumerable<IGrouping<TKey, TElement>>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(keySelector), 'keySelector');
  TArgument.CheckNotNull(Assigned(elementSelector), 'elementSelector');

  Result := TGroupedEnumerable<T, TKey, TElement>.Create(
    Source, keySelector, elementSelector, comparer);
end;
{$ENDIF}

function Enumerable<T>.GroupBy<TKey, TResult>(const keySelector: TFunc<T, TKey>;
  const resultSelector: TFunc<TKey, IEnumerable<T>, TResult>): Enumerable<TResult>;
begin
  Result := GroupBy<TKey, T, TResult>(
    keySelector, IdentityFunction, resultSelector, nil);
end;

function Enumerable<T>.GroupBy<TKey, TResult>(const keySelector: TFunc<T, TKey>;
  const resultSelector: TFunc<TKey, IEnumerable<T>, TResult>;
  const comparer: IEqualityComparer<TKey>): Enumerable<TResult>;
begin
  Result := GroupBy<TKey, T, TResult>(
    keySelector, IdentityFunction, resultSelector, comparer);
end;

function Enumerable<T>.GroupBy<TKey, TElement, TResult>(
  const keySelector: TFunc<T, TKey>; const elementSelector: TFunc<T, TElement>;
  const resultSelector: TFunc<TKey, IEnumerable<TElement>, TResult>): Enumerable<TResult>;
begin
  Result := GroupBy<TKey, TElement, TResult>(
    keySelector, elementSelector, resultSelector, nil);
end;

function Enumerable<T>.GroupBy<TKey, TElement, TResult>(
  const keySelector: TFunc<T, TKey>; const elementSelector: TFunc<T, TElement>;
  const resultSelector: TFunc<TKey, IEnumerable<TElement>, TResult>;
  const comparer: IEqualityComparer<TKey>): Enumerable<TResult>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(keySelector), 'keySelector');
  TArgument.CheckNotNull(Assigned(elementSelector), 'elementSelector');
  TArgument.CheckNotNull(Assigned(resultSelector), 'resultSelector');

  Result := TGroupedEnumerable<T, TKey, TElement, TResult>.Create(
    Source, keySelector, elementSelector, resultSelector, comparer);
end;

function Enumerable<T>.GroupJoin<TInner, TKey, TResult>(
  const inner: IEnumerable<TInner>; const outerKeySelector: TFunc<T, TKey>;
  const innerKeySelector: TFunc<TInner, TKey>;
  const resultSelector: TFunc<T, IEnumerable<TInner>, TResult>): Enumerable<TResult>;
begin
  Result := GroupJoin<TInner, TKey, TResult>(
    inner, outerKeySelector, innerKeySelector, resultSelector, nil);
end;

function Enumerable<T>.GroupJoin<TInner, TKey, TResult>(
  const inner: IEnumerable<TInner>; const outerKeySelector: TFunc<T, TKey>;
  const innerKeySelector: TFunc<TInner, TKey>;
  const resultSelector: TFunc<T, IEnumerable<TInner>, TResult>;
  const comparer: IEqualityComparer<TKey>): Enumerable<TResult>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'outer');
  TArgument.CheckNotNull(Assigned(inner), 'inner');
  TArgument.CheckNotNull(Assigned(outerKeySelector), 'outerKeySelector');
  TArgument.CheckNotNull(Assigned(innerKeySelector), 'innerKeySelector');
  TArgument.CheckNotNull(Assigned(resultSelector), 'resultSelector');

  Result := TGroupJoinIterator<T, TInner, TKey, TResult>.Create(
    Source, inner, outerKeySelector, innerKeySelector, resultSelector, comparer);
end;

function Enumerable<T>.IntersectWith(const second: array of T): Enumerable<T>;
begin
  Result := IntersectWith(TArrayIterator<T>.Create(second));
end;

function Enumerable<T>.IntersectWith(
  const second: IEnumerable<T>): Enumerable<T>;
begin
  Result := IntersectWith(second, nil);
end;

function Enumerable<T>.IntersectWith(const second: IEnumerable<T>;
  const comparer: IEqualityComparer<T>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'first');
  TArgument.CheckNotNull(Assigned(second), 'second');

  Result := TIntersectIterator<T>.Create(Source, second, comparer);
end;

function Enumerable<T>.Join<TInner, TKey, TResult>(
  const inner: IEnumerable<TInner>; const outerKeySelector: TFunc<T, TKey>;
  const innerKeySelector: TFunc<TInner, TKey>;
  const resultSelector: TFunc<T, TInner, TResult>): Enumerable<TResult>;
begin
  Result := Join<TInner, TKey, TResult>(
    inner, outerKeySelector, innerKeySelector, resultSelector, nil);
end;

function Enumerable<T>.Join<TInner, TKey, TResult>(
  const inner: IEnumerable<TInner>; const outerKeySelector: TFunc<T, TKey>;
  const innerKeySelector: TFunc<TInner, TKey>;
  const resultSelector: TFunc<T, TInner, TResult>;
  const comparer: IEqualityComparer<TKey>): Enumerable<TResult>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'outer');
  TArgument.CheckNotNull(Assigned(inner), 'inner');
  TArgument.CheckNotNull(Assigned(outerKeySelector), 'outerKeySelector');
  TArgument.CheckNotNull(Assigned(innerKeySelector), 'innerKeySelector');
  TArgument.CheckNotNull(Assigned(resultSelector), 'resultSelector');

  Result := TJoinIterator<T, TInner, TKey, TResult>.Create(
    Source, inner, outerKeySelector, innerKeySelector, resultSelector, comparer);
end;

function Enumerable<T>.Last: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.Last;
end;

function Enumerable<T>.Last(const predicate: TPredicate<T>): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := Source.Last(predicate);
end;

function Enumerable<T>.LastOrDefault: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.LastOrDefault;
end;

function Enumerable<T>.LastOrDefault(const defaultValue: T): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.LastOrDefault(defaultValue);
end;

function Enumerable<T>.LastOrDefault(const predicate: TPredicate<T>): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := Source.LastOrDefault(predicate);
end;

function Enumerable<T>.LastOrDefault(const predicate: TPredicate<T>;
  const defaultValue: T): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := Source.LastOrDefault(predicate, defaultValue);
end;

function Enumerable<T>.Max: T;
var
  comparer: IComparer<T>;
  flag: Boolean;
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  comparer := Source.GetComparer;
  flag := False;
  for item in Source do
  begin
    if flag then
    begin
      if comparer.Compare(item, Result) > 0 then
        Result := item;
    end
    else
    begin
      flag := True;
      Result := item;
    end;
  end;
  if not flag then
    raise EInvalidOperationException.CreateRes(@SNoElements);
end;

function Enumerable<T>.Max(const selector: TFunc<T, Double>): Double;
begin
  Result := Select<Double>(selector).Max;
end;

function Enumerable<T>.Max(const selector: TFunc<T, Int64>): Int64;
begin
  Result := Select<Int64>(selector).Max;
end;

function Enumerable<T>.MaxBy<TKey>(
  const keySelector: TFunc<T, TKey>): Enumerable<T>;
begin
  Result := MaxBy<TKey>(keySelector, TComparer<TKey>.Default);
end;

function Enumerable<T>.MaxBy<TKey>(const keySelector: TFunc<T, TKey>;
  const comparer: IComparer<TKey>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(keySelector), 'keySelector');

  Result := TExtremaByIterator<T, TKey>.Create(Source, keySelector,
    function(key, minValue: TKey): Integer
    begin
      Result := comparer.Compare(key, minValue);
    end);
end;

function Enumerable<T>.Min: T;
var
  comparer: IComparer<T>;
  flag: Boolean;
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  comparer := Source.GetComparer;
  flag := False;
  for item in Source do
  begin
    if flag then
    begin
      if comparer.Compare(item, Result) < 0 then
        Result := item;
    end
    else
    begin
      flag := True;
      Result := item;
    end;
  end;
  if not flag then
    raise EInvalidOperationException.CreateRes(@SNoElements);
end;

function Enumerable<T>.Min(const selector: TFunc<T, Double>): Double;
begin
  Result := Select<Double>(selector).Min;
end;

function Enumerable<T>.Min(const selector: TFunc<T, Int64>): Int64;
begin
  Result := Select<Int64>(selector).Min;
end;

function Enumerable<T>.MinBy<TKey>(
  const keySelector: TFunc<T, TKey>): Enumerable<T>;
begin
  Result := MinBy<TKey>(keySelector, TComparer<TKey>.Default);
end;

function Enumerable<T>.MinBy<TKey>(const keySelector: TFunc<T, TKey>;
  const comparer: IComparer<TKey>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(keySelector), 'keySelector');

  Result := TExtremaByIterator<T, TKey>.Create(Source, keySelector,
    function(key, minValue: TKey): Integer
    begin
      Result := -comparer.Compare(key, minValue);
    end);
end;

function Enumerable<T>.OrderBy<TKey>(
  const keySelector: TFunc<T, TKey>): Enumerable<T>;
begin
  Result := OrderBy<TKey>(keySelector, nil);
end;

function Enumerable<T>.OrderBy<TKey>(const keySelector: TFunc<T, TKey>;
  const comparer: IComparer<TKey>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(keySelector), 'keySelector');

  Result := TOrderedEnumerable<T, TKey>.Create(Source, keySelector, comparer, False);
end;

function Enumerable<T>.OrderByDescending<TKey>(
  const keySelector: TFunc<T, TKey>): Enumerable<T>;
begin
  Result := OrderByDescending<TKey>(keySelector, nil);
end;

function Enumerable<T>.OrderByDescending<TKey>(
  const keySelector: TFunc<T, TKey>;
  const comparer: IComparer<TKey>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(keySelector), 'keySelector');

  Result := TOrderedEnumerable<T, TKey>.Create(Source, keySelector, comparer, True);
end;

function Enumerable<T>.Reverse: Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := TReverseIterator<T>.Create(Source);
end;

function Enumerable<T>.Select(const selector: TFunc<T, T>): Enumerable<T>;
begin
  Result := Select<T>(selector);
end;

function Enumerable<T>.Select<TResult>(
  const selector: TFunc<T, TResult>): Enumerable<TResult>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(selector), 'selector');

  Result := TSelectEnumerableIterator<T, TResult>.Create(Source, selector);
end;

function Enumerable<T>.Select(
  const selector: TFunc<T, Integer, T>): Enumerable<T>;
begin
  Result := Select<T>(selector);
end;

function Enumerable<T>.Select<TResult>(
  const selector: TFunc<T, Integer, TResult>): Enumerable<TResult>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(selector), 'selector');

  Result := TSelectIterator<T, TResult>.Create(Source, selector);
end;

function Enumerable<T>.SelectMany<TResult>(
  const selector: TFunc<T, IEnumerable<TResult>>): Enumerable<TResult>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(selector), 'selector');

  Result := TSelectManyIterator<T, TResult>.Create(Source, selector);
end;

function Enumerable<T>.SelectMany<TResult>(
  const selector: TFunc<T, Integer, IEnumerable<TResult>>): Enumerable<TResult>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(selector), 'selector');

  Result := TSelectManyIndexIterator<T, TResult>.Create(Source, selector);
end;

function Enumerable<T>.SelectMany<TCollection, TResult>(
  const collectionSelector: TFunc<T, IEnumerable<TCollection>>;
  const resultSelector: TFunc<T, TCollection, TResult>): Enumerable<TResult>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(collectionSelector), 'collectionSelector');
  TArgument.CheckNotNull(Assigned(resultSelector), 'resultSelector');

  Result := TSelectManyIterator<T, TCollection, TResult>.Create(
    Source, collectionSelector, resultSelector);
end;

function Enumerable<T>.SelectMany<TCollection, TResult>(
  const collectionSelector: TFunc<T, Integer, IEnumerable<TCollection>>;
  const resultSelector: TFunc<T, TCollection, TResult>): Enumerable<TResult>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(collectionSelector), 'collectionSelector');
  TArgument.CheckNotNull(Assigned(resultSelector), 'resultSelector');

  Result := TSelectManyIndexIterator<T, TCollection, TResult>.Create(
    Source, collectionSelector, resultSelector);
end;

function Enumerable<T>.SequenceEqual(const second: array of T): Boolean;
begin
  Result := SequenceEqual(TArrayIterator<T>.Create(second), nil);
end;

function Enumerable<T>.SequenceEqual(const second: IEnumerable<T>): Boolean;
begin
  Result := SequenceEqual(second, nil);
end;

function Enumerable<T>.SequenceEqual(const second: IEnumerable<T>;
  comparer: IEqualityComparer<T>): Boolean;
var
  enumerator1, enumerator2: IEnumerator<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'first');
  TArgument.CheckNotNull(Assigned(second), 'second');

  if not Assigned(comparer) then
    comparer := TEqualityComparer<T>.Default;

  enumerator1 := Source.GetEnumerator;
  enumerator2 := second.GetEnumerator;
  while enumerator1.MoveNext do
    if not enumerator2.MoveNext or not comparer.Equals(enumerator1.Current, enumerator2.Current) then
      Exit(False);
  if enumerator2.MoveNext then
    Exit(False);
  Result := True;
end;

function Enumerable<T>.Single: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.Single;
end;

function Enumerable<T>.Single(const predicate: TPredicate<T>): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := Source.Single(predicate);
end;

function Enumerable<T>.SingleOrDefault: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.SingleOrDefault;
end;

function Enumerable<T>.SingleOrDefault(const defaultValue: T): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.SingleOrDefault(defaultValue);
end;

function Enumerable<T>.SingleOrDefault(const predicate: TPredicate<T>): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := Source.SingleOrDefault(predicate);
end;

function Enumerable<T>.SingleOrDefault(const predicate: TPredicate<T>;
  const defaultValue: T): T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := Source.SingleOrDefault(predicate, defaultValue);
end;

function Enumerable<T>.Skip(count: Integer): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := TSkipIterator<T>.Create(Source, count);
end;

function Enumerable<T>.SkipWhile(const predicate: TPredicate<T>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := TSkipWhileIterator<T>.Create(Source, predicate);
end;

function Enumerable<T>.SkipWhile(
  const predicate: TFunc<T, Integer, Boolean>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := TSkipWhileIndexIterator<T>.Create(Source, predicate);
end;

function Enumerable<T>.Sum: T;
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  // TODO: support Nullable<>
  Result := Default(T);

  for item in Source do
    Result := Add(Result, item);
end;

function Enumerable<T>.Sum(const selector: TFunc<T, Integer>): Integer;
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(selector), 'selector');

  Result := 0;
  for item in Source do
    Result := Result + selector(item);
end;

function Enumerable<T>.Sum(
  const selector: TFunc<T, Nullable<Integer>>): Nullable<Integer>;
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(selector), 'selector');

  Result := 0;
  for item in Source do
    Result := Result.Value + selector(item).GetValueOrDefault;
end;

function Enumerable<T>.Sum(const selector: TFunc<T, Int64>): Int64;
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(selector), 'selector');

  Result := 0;
  for item in Source do
    Result := Result + selector(item);
end;

function Enumerable<T>.Sum(
  const selector: TFunc<T, Nullable<Int64>>): Nullable<Int64>;
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(selector), 'selector');

  Result := 0;
  for item in Source do
    Result := Result.Value + selector(item).GetValueOrDefault;
end;

function Enumerable<T>.Sum(const selector: TFunc<T, Single>): Single;
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(selector), 'selector');

  Result := 0;
  for item in Source do
    Result := Result + selector(item);
end;

function Enumerable<T>.Sum(
  const selector: TFunc<T, Nullable<Single>>): Nullable<Single>;
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(selector), 'selector');

  Result := 0;
  for item in Source do
    Result := Result.Value + selector(item).GetValueOrDefault;
end;

function Enumerable<T>.Sum(const selector: TFunc<T, Double>): Double;
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(selector), 'selector');

  Result := 0;
  for item in Source do
    Result := Result + selector(item);
end;

function Enumerable<T>.Sum(
  const selector: TFunc<T, Nullable<Double>>): Nullable<Double>;
var
  item: T;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(selector), 'selector');

  Result := 0;
  for item in Source do
    Result := Result.Value + selector(item).GetValueOrDefault;
end;

function Enumerable<T>.Take(count: Integer): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := TTakeIterator<T>.Create(Source, count);
end;

function Enumerable<T>.TakeWhile(const predicate: TPredicate<T>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := TTakeWhileIterator<T>.Create(Source, predicate);
end;

function Enumerable<T>.TakeWhile(
  const predicate: TFunc<T, Integer, Boolean>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := TTakeWhileIndexIterator<T>.Create(Source, predicate);
end;

function Enumerable<T>.ThenBy<TKey>(
  const keySelector: TFunc<T, TKey>): Enumerable<T>;
begin
  Result := ThenBy<TKey>(keySelector, nil);
end;

function Enumerable<T>.ThenBy<TKey>(const keySelector: TFunc<T, TKey>;
  const comparer: IComparer<TKey>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(keySelector), 'keySelector');

  Result := TOrderedEnumerable<T, TKey>.Create(Source, keySelector, comparer, False);
end;

function Enumerable<T>.ThenByDescending<TKey>(
  const keySelector: TFunc<T, TKey>): Enumerable<T>;
begin
  Result := ThenByDescending<TKey>(keySelector, nil);
end;

function Enumerable<T>.ThenByDescending<TKey>(const keySelector: TFunc<T, TKey>;
  const comparer: IComparer<TKey>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(keySelector), 'keySelector');

  Result := TOrderedEnumerable<T, TKey>.Create(Source, keySelector, comparer, True);
end;

function Enumerable<T>.ToArray: TArray<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.ToArray;
end;

function Enumerable<T>.ToList: IList<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := TCollections.CreateList<T>;
  Result.AddRange(Source);
end;

function Enumerable<T>.ToLookup(
  const keySelector: TFunc<T, T>): ILookup<T, T>;
begin
  Result := ToLookup<T, T>(keySelector, IdentityFunction, nil);
end;

function Enumerable<T>.ToLookup<TKey>(
  const keySelector: TFunc<T, TKey>): ILookup<TKey, T>;
begin
  Result := ToLookup<TKey, T>(keySelector, IdentityFunction, nil);
end;

function Enumerable<T>.ToLookup(const keySelector: TFunc<T, T>;
  const comparer: IEqualityComparer<T>): ILookup<T, T>;
begin
  Result := ToLookup<T, T>(keySelector, IdentityFunction, comparer);
end;

function Enumerable<T>.ToLookup<TKey>(const keySelector: TFunc<T, TKey>;
  const comparer: IEqualityComparer<TKey>): ILookup<TKey, T>;
begin
  Result := ToLookup<TKey, T>(keySelector, IdentityFunction, comparer);
end;

function Enumerable<T>.ToLookup<TKey, TElement>(
  const keySelector: TFunc<T, TKey>;
  const elementSelector: TFunc<T, TElement>): ILookup<TKey, TElement>;
begin
  Result := ToLookup<TKey, TElement>(keySelector, elementSelector, nil);
end;

function Enumerable<T>.ToLookup<TKey, TElement>(
  const keySelector: TFunc<T, TKey>; const elementSelector: TFunc<T, TElement>;
  const comparer: IEqualityComparer<TKey>): ILookup<TKey, TElement>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(keySelector), 'keySelector');
  TArgument.CheckNotNull(Assigned(elementSelector), 'elementSelector');

  Result := TLookup<TKey, TElement>.Create<T>(Source, keySelector, elementSelector, comparer);
end;

function Enumerable<T>.ToSet: ISet<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := TCollections.CreateSet<T>;
  Result.AddRange(Source);
end;

function Enumerable<T>.TryGetFirst(out value: T): Boolean;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.TryGetFirst(value);
end;

function Enumerable<T>.TryGetFirst(out value: T;
  const predicate: TPredicate<T>): Boolean;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.TryGetFirst(value, predicate);
end;

function Enumerable<T>.TryGetLast(out value: T): Boolean;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.TryGetLast(value);
end;

function Enumerable<T>.TryGetLast(out value: T;
  const predicate: TPredicate<T>): Boolean;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');

  Result := Source.TryGetLast(value, predicate);
end;

function Enumerable<T>.UnionWith(const second: array of T): Enumerable<T>;
begin
  Result := UnionWith(TArrayIterator<T>.Create(second));
end;

function Enumerable<T>.UnionWith(const second: IEnumerable<T>): Enumerable<T>;
begin
  Result := UnionWith(second, nil);
end;

function Enumerable<T>.UnionWith(const second: IEnumerable<T>;
  const comparer: IEqualityComparer<T>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'first');
  TArgument.CheckNotNull(Assigned(second), 'second');

  Result := TUnionIterator<T>.Create(Source, second, comparer);
end;

function Enumerable<T>.Where(const predicate: TPredicate<T>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := TWhereEnumerableIterator<T>.Create(Source, predicate);
end;

function Enumerable<T>.Where(
  const predicate: TFunc<T, Integer, Boolean>): Enumerable<T>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'source');
  TArgument.CheckNotNull(Assigned(predicate), 'predicate');

  Result := TWhereIterator<T>.Create(Source, predicate);
end;

function Enumerable<T>.Zip<TSecond, TResult>(const second: IEnumerable<TSecond>;
  const resultSelector: TFunc<T, TSecond, TResult>): Enumerable<TResult>;
begin
  TArgument.CheckNotNull(Assigned(fSource), 'first');
  TArgument.CheckNotNull(Assigned(second), 'second');
  TArgument.CheckNotNull(Assigned(resultSelector), 'resultSelector');

  Result := TZipIterator<T, TSecond, TResult>.Create(
    Source, second, resultSelector);
end;

{$ENDREGION}


{$REGION 'Enumerable'}

class function Enumerable.Empty<T>: Enumerable<T>;
begin
  Result := Enumerable<T>.Create;
end;

class function Enumerable.Range(start, count: Integer): Enumerable<Integer>;
begin
  Result := TRangeIterator<Integer>.Create(start, count);
end;

{$ENDREGION}


end.
